package co.dianjiu.nio.server;

import co.dianjiu.nio.util.InputUtils;

import java.io.IOException;
import java.io.PrintStream;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set;

/**
 * @author DianJiu
 * @website https://dianjiu.co
 * @email dianjiu@dianjiu.cc
 * @date 2021/8/2 17:37
 * @desc TODO
 */
public class NIOServerHandler{
    private Selector client ;
    private Scanner scanner;
    private PrintStream out;
    private boolean flag = true;

    public NIOServerHandler(Selector client) {
        this.client = client;
    }

    public void listen() throws IOException{
        //死循环一直监听
        while(flag){
            //获取检测到的连接完成，因为此时只注册了 OP_ACCEPT事件ssc.register(selector,SelectionKey.OP_ACCEPT);
            //所以就相当于传统socket中的accpet方法，只是这个select会一次返回所有accept状态的连接，而非传统socket中的阻塞式单个accept方法
            int select = client.select();
            //没有建立连接的则直接下一次循环
            if(select == 0){
                continue;
            }
            Set<SelectionKey> selectedKeys = client.selectedKeys();
            Iterator<SelectionKey> it = selectedKeys.iterator();
            while(it.hasNext()){
                SelectionKey key = it.next();
                // 由于select操作只管对selectedKeys进行添加，所以key处理后我们需要从里面把key去掉
                it.remove();
                if(key.isAcceptable()){
                    //获取通道,相当于传统io的iostream作用，不同的是channel是批量传输buffer对象的，而对于buffer是可以0拷贝的，因此速度会相对有提升
                    ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
                    // 得到与客户端的套接字通道，没啥好说的
                    SocketChannel channel = serverChannel.accept();
                    //设置channel非阻塞
                    channel.configureBlocking(false);
                    //注册读事件，会覆盖之前注册的OP_ACCEPT事件，因为模仿http请求单次请求，单次响应，所以作为服务端肯定是先注册读事件用于处理。
                    channel.register(client, SelectionKey.OP_READ);
                    //即先请求在得到响应数据，如果是想保持长连接则写成如下形式
                    //channel.register(client, SelectionKey.OP_READ|SelectionKey.OP_WRITE);
                }
                //当注册完读事件，下一次循环读就绪时就会进入此方法
                if(key.isReadable()){
                    //读取对应channel事件
                    read(key);
                    //改变自身关注事件，可以用位或操作|组合时间，读完之后注册写事件模拟http的响应，所以用写事件覆盖读事件
                    key.interestOps(SelectionKey.OP_WRITE);
                }
                //处理完读会注册写事件，下一次循环会进入此方法
                if(key.isWritable()){
                    //处理写事件
                    String write = write(key);
                    //完成写之后，保持长连接，再次切换读状态
                    key.interestOps(SelectionKey.OP_READ);
                    // 改变自身关注事件，此处为清理所有监听事件，模拟http中相应完关闭此连接
                    if("886".equalsIgnoreCase(write)){
                        //清理事件监听
                        key.interestOps(0);
                        //关闭连接
                        key.channel().close();
                    }
                }
            }
        }
    }

    private String write(SelectionKey key) throws IOException {
        //获取控制台输入流
        Scanner scan = new Scanner(System.in);
        scan.useDelimiter("\n");
        String replyData = InputUtils.getString("请输入要回复的内容：").trim();
        //回复请求
        SocketChannel channel = (SocketChannel) key.channel();
        //此处自行搜索ByteBuffer相关使用
        channel.write(ByteBuffer.wrap(replyData.getBytes()));
        return replyData;
    }

    private void read(SelectionKey key) throws IOException {
        SocketChannel channel = (SocketChannel) key.channel();
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        int count = channel.read(buffer);
        byte[] bytes = new byte[count];
        buffer.flip();
        buffer.get(bytes);
        System.out.println("收到客户端发来消息：\n" + new String(bytes));
    }
}
